/*
 * Conditions Of Use
 *
 * This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 Untied States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 *
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof, including but
 * not limited to the correctness, accuracy, reliability or usefulness of
 * the software.
 *
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement
 *
 * .
 *
 */
/******************************************************************************
 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD).      *
 ******************************************************************************/
package gov.nist.javax.sip;

import gov.nist.core.InternalErrorHandler;
import gov.nist.javax.sip.address.RouterExt;
import gov.nist.javax.sip.header.CallID;
import gov.nist.javax.sip.header.Via;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import gov.nist.javax.sip.stack.HopImpl;
import gov.nist.javax.sip.stack.MessageChannel;
import gov.nist.javax.sip.stack.SIPClientTransaction;
import gov.nist.javax.sip.stack.SIPDialog;
import gov.nist.javax.sip.stack.SIPServerTransaction;
import gov.nist.javax.sip.stack.SIPTransaction;
import gov.nist.javax.sip.stack.SIPTransactionErrorEvent;
import gov.nist.javax.sip.stack.SIPTransactionEventListener;

import java.io.IOException;
import java.text.ParseException;
import java.util.EventObject;
import java.util.Iterator;
import java.util.TooManyListenersException;
import java.util.concurrent.ConcurrentHashMap;

import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.ObjectInUseException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipListener;
import javax.sip.SipStack;
import javax.sip.Timeout;
import javax.sip.TimeoutEvent;
import javax.sip.Transaction;
import javax.sip.TransactionAlreadyExistsException;
import javax.sip.TransactionState;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.Hop;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

/*
 * Contributions (bug fixes) made by: Daniel J. Martinez Manzano, Hagai Sela.
 * Bug reports by Shanti Kadiyala, Rhys Ulerich,Victor Hugo
 */
/**
 * Implementation of the JAIN-SIP provider interface.
 * 
 * @version 1.2 $Revision: 1.61 $ $Date: 2009/05/20 06:10:01 $
 * 
 * @author M. Ranganathan <br/>
 * 
 * 
 */

public final class SipProviderImpl implements javax.sip.SipProvider,
		SIPTransactionEventListener {

	protected SipListener sipListener;

	protected SipStackImpl sipStack;

	/*
	 * A set of listening points associated with the provider At most one LP per
	 * transport
	 */
	private ConcurrentHashMap listeningPoints;

	private EventScanner eventScanner;

	private String address;

	private int port;

	private boolean automaticDialogSupportEnabled;

	/**
	 * A string containing the 0.0.0.0 IPv4 ANY address.
	 */
	private String IN_ADDR_ANY = "0.0.0.0";

	/**
	 * A string containing the ::0 IPv6 ANY address.
	 */
	private String IN6_ADDR_ANY = "::0";	
	
	/**
	 * Stop processing messages for this provider. Post an empty message to our
	 * message processing queue that signals us to quit.
	 */
	protected void stop() {
		// Put an empty event in the queue and post ourselves a message.
		if (sipStack.isLoggingEnabled())
			sipStack.getLogWriter().logDebug("Exiting provider");
		for (Iterator it = listeningPoints.values().iterator(); it.hasNext();) {
			ListeningPointImpl listeningPoint = (ListeningPointImpl) it.next();
			listeningPoint.removeSipProvider();
		}
		this.eventScanner.stop();

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getListeningPoint(java.lang.String)
	 */
	public ListeningPoint getListeningPoint(String transport) {
		if (transport == null)
			throw new NullPointerException("Null transport param");
		return (ListeningPoint) this.listeningPoints.get(transport
				.toUpperCase());
	}

	/**
	 * Handle the SIP event - because we have only one listener and we are
	 * already in the context of a separate thread, we dont need to enque the
	 * event and signal another thread.
	 * 
	 * @param sipEvent
	 *            is the event to process.
	 * 
	 */

	public void handleEvent(EventObject sipEvent, SIPTransaction transaction) {
		if (sipStack.isLoggingEnabled()) {
			sipStack.getLogWriter().logDebug(
					"handleEvent " + sipEvent + "currentTransaction = "
							+ transaction + "this.sipListener = "
							+ this.sipListener + "sipEvent.source = "
							+ sipEvent.getSource());
			if (sipEvent instanceof RequestEvent) {
				Dialog dialog = ((RequestEvent) sipEvent).getDialog();
				if ( sipStack.isLoggingEnabled())  sipStack.getLogWriter().logDebug("Dialog = " + dialog);
			} else if (sipEvent instanceof ResponseEvent) {
				Dialog dialog = ((ResponseEvent) sipEvent).getDialog();
				if (sipStack.isLoggingEnabled() ) sipStack.getLogWriter().logDebug("Dialog = " + dialog);
			}
			sipStack.getLogWriter().logStackTrace();
		}

		EventWrapper eventWrapper = new EventWrapper(sipEvent, transaction);

		if (!sipStack.reEntrantListener) {
			// Run the event in the context of a single thread.
			this.eventScanner.addEvent(eventWrapper);
		} else {
			// just call the delivery method
			this.eventScanner.deliverEvent(eventWrapper);
		}
	}

	/** Creates a new instance of SipProviderImpl */
	protected SipProviderImpl(SipStackImpl sipStack) {
		this.eventScanner = sipStack.getEventScanner(); // for quick access.
		this.sipStack = sipStack;
		this.eventScanner.incrementRefcount();
		this.listeningPoints = new ConcurrentHashMap<String,ListeningPointImpl>();
		this.automaticDialogSupportEnabled = this.sipStack
				.isAutomaticDialogSupportEnabled();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#clone()
	 */
	protected Object clone() throws java.lang.CloneNotSupportedException {
		throw new java.lang.CloneNotSupportedException();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object obj) {
		return super.equals(obj);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#addSipListener(javax.sip.SipListener)
	 */
	public void addSipListener(SipListener sipListener)
			throws TooManyListenersException {

		if (sipStack.sipListener == null) {
			sipStack.sipListener = sipListener;
		} else if (sipStack.sipListener != sipListener) {
			throw new TooManyListenersException(
					"Stack already has a listener. Only one listener per stack allowed");
		}

		if (sipStack.isLoggingEnabled())
			sipStack.getLogWriter().logDebug("add SipListener " + sipListener);
		this.sipListener = sipListener;

	}

	/*
	 * This method is deprecated (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getListeningPoint()
	 */

	public ListeningPoint getListeningPoint() {
		if (this.listeningPoints.size() > 0)
			return (ListeningPoint) this.listeningPoints.values().iterator()
					.next();
		else
			return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getNewCallId()
	 */
	public CallIdHeader getNewCallId() {
		String callId = Utils.getInstance().generateCallIdentifier(this.getListeningPoint()
				.getIPAddress());
		CallID callid = new CallID();
		try {
			callid.setCallId(callId);
		} catch (java.text.ParseException ex) {
		}
		return callid;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getNewClientTransaction(javax.sip.message.Request)
	 */
	public ClientTransaction getNewClientTransaction(Request request)
			throws TransactionUnavailableException {
		if (request == null)
			throw new NullPointerException("null request");
		if (!sipStack.isAlive())
			throw new TransactionUnavailableException("Stack is stopped");

		SIPRequest sipRequest = (SIPRequest) request;
		if (sipRequest.getTransaction() != null)
			throw new TransactionUnavailableException(
					"Transaction already assigned to request");
		if ( sipRequest.getMethod().equals(Request.ACK)) {
			throw new TransactionUnavailableException ("Cannot create client transaction for  " + Request.ACK);
		}
		// Be kind and assign a via header for this provider if the user is
		// sloppy
		if (sipRequest.getTopmostVia() == null) {
			ListeningPointImpl lp = (ListeningPointImpl) this
					.getListeningPoint("udp");
			Via via = lp.getViaHeader();
			request.setHeader(via);
		}
		// Give the request a quick check to see if all headers are assigned.
		try {
			sipRequest.checkHeaders();
		} catch (ParseException ex) {
			throw new TransactionUnavailableException(ex.getMessage(), ex);
		}

		/*
		 * User decided to give us his own via header branch. Lets see if it
		 * results in a clash. If so reject the request.
		 */
		if (sipRequest.getTopmostVia().getBranch() != null
				&& sipRequest.getTopmostVia().getBranch().startsWith(
						SIPConstants.BRANCH_MAGIC_COOKIE)
				&& sipStack.findTransaction((SIPRequest) request, false) != null) {
			throw new TransactionUnavailableException(
					"Transaction already exists!");
		}

		
		

		if (request.getMethod().equalsIgnoreCase(Request.CANCEL)) {
			SIPClientTransaction ct = (SIPClientTransaction) sipStack
					.findCancelTransaction((SIPRequest) request, false);
			if (ct != null) {
				ClientTransaction retval = sipStack.createClientTransaction(
						(SIPRequest) request, ct.getMessageChannel());

				((SIPTransaction) retval).addEventListener(this);
				sipStack.addTransaction((SIPClientTransaction) retval);
				if (ct.getDialog() != null) {
					((SIPClientTransaction) retval).setDialog((SIPDialog) ct
							.getDialog(), sipRequest.getDialogId(false));

				}
				return retval;
			}

		}
		if (sipStack.isLoggingEnabled())
			sipStack.getLogWriter().logDebug(
					"could not find existing transaction for "
							+ ((SIPRequest) request).getFirstLine()
							+ " creating a new one ");

		// Could not find a dialog or the route is not set in dialog.

		Hop hop = null;
		try {
			hop = sipStack.getNextHop((SIPRequest) request);
			if (hop == null)
				throw new TransactionUnavailableException(
						"Cannot resolve next hop -- transaction unavailable");
		} catch (SipException ex) {
			throw new TransactionUnavailableException(
					"Cannot resolve next hop -- transaction unavailable", ex);
		}
		String transport = hop.getTransport();
        ListeningPointImpl listeningPoint = (ListeningPointImpl) this
                .getListeningPoint(transport);
        
		String dialogId = sipRequest.getDialogId(false);
		SIPDialog dialog = sipStack.getDialog(dialogId);
		if (dialog != null && dialog.getState() == DialogState.TERMINATED) {

			// throw new TransactionUnavailableException
			// ("Found a terminated dialog -- possible re-use of old tag
			// parameters");
			sipStack.removeDialog(dialog);

		}

		// An out of dialog route was found. Assign this to the
		// client transaction.

		try {
			// Set the brannch id before you ask for a tx.
			// If the user has set his own branch Id and the
			// branch id starts with a valid prefix, then take it.
			// otherwise, generate one.
			String branchId = null;
			if (sipRequest.getTopmostVia().getBranch() == null
					|| !sipRequest.getTopmostVia().getBranch().startsWith(
							SIPConstants.BRANCH_MAGIC_COOKIE)) {
				branchId = Utils.getInstance().generateBranchId();

				sipRequest.getTopmostVia().setBranch(branchId);
			}
			Via topmostVia = sipRequest.getTopmostVia();
	        topmostVia.setTransport(transport);
	        topmostVia.setPort(listeningPoint.getPort());
			branchId = sipRequest.getTopmostVia().getBranch();
			
			SIPClientTransaction ct = (SIPClientTransaction) sipStack
					.createMessageChannel(sipRequest, listeningPoint
							.getMessageProcessor(), hop);
			if (ct == null)
				throw new TransactionUnavailableException("Cound not create tx");
			ct.setNextHop(hop);
			ct.setOriginalRequest(sipRequest);
			ct.setBranch(branchId);
			// if the stack supports dialogs then
			if (sipStack.isDialogCreated(request.getMethod())) {
				// create a new dialog to contain this transaction
				// provided this is necessary.
				// This could be a re-invite
				// in which case the dialog is re-used.
				// (but noticed by Brad Templeton)
				if (dialog != null)
					ct.setDialog(dialog, sipRequest.getDialogId(false));
				else if (this.isAutomaticDialogSupportEnabled()) {
					SIPDialog sipDialog = sipStack.createDialog(ct);
					ct.setDialog(sipDialog, sipRequest.getDialogId(false));
				}
			} else {
				if (dialog != null) {
					ct.setDialog(dialog, sipRequest.getDialogId(false));
				}

			}

			// The provider is the event listener for all transactions.
			ct.addEventListener(this);
			return (ClientTransaction) ct;
		} catch (IOException ex) {

			throw new TransactionUnavailableException(
					"Could not resolve next hop or listening point unavailable! ",
					ex);

		} catch (java.text.ParseException ex) {
			InternalErrorHandler.handleException(ex);
			throw new TransactionUnavailableException(
					"Unexpected Exception FIXME! ", ex);
		} catch (InvalidArgumentException ex) {
		    InternalErrorHandler.handleException(ex);
            throw new TransactionUnavailableException(
                    "Unexpected Exception FIXME! ", ex);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getNewServerTransaction(javax.sip.message.Request)
	 */
	public ServerTransaction getNewServerTransaction(Request request)
			throws TransactionAlreadyExistsException,
			TransactionUnavailableException {

		if (!sipStack.isAlive())
			throw new TransactionUnavailableException("Stack is stopped");
		SIPServerTransaction transaction = null;
		SIPRequest sipRequest = (SIPRequest) request;
		try {
			sipRequest.checkHeaders();
		} catch (ParseException ex) {
			throw new TransactionUnavailableException(ex.getMessage(), ex);
		}

		if ( request.getMethod().equals(Request.ACK)) {
			if ( sipStack.isLoggingEnabled())
				sipStack.getLogWriter().logError("Creating server transaction for ACK -- makes no sense!");
			throw new TransactionUnavailableException("Cannot create Server transaction for ACK ");
		}
		/*
		 * Got a notify.
		 */
		if (sipRequest.getMethod().equals(Request.NOTIFY)
				&& sipRequest.getFromTag() != null
				&& sipRequest.getToTag() == null) {

			SIPClientTransaction ct = sipStack.findSubscribeTransaction(
					sipRequest, (ListeningPointImpl) this.getListeningPoint());
			/* Issue 104 */
			if (ct == null && ! sipStack.deliverUnsolicitedNotify) {
				throw new TransactionUnavailableException(
						"Cannot find matching Subscription (and gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY not set)");
			}
		}
		if (sipStack.isDialogCreated(sipRequest.getMethod())) {
			if (sipStack.findTransaction((SIPRequest) request, true) != null)
				throw new TransactionAlreadyExistsException(
						"server transaction already exists!");

			transaction = (SIPServerTransaction) ((SIPRequest) request)
					.getTransaction();
			if (transaction == null)
				throw new TransactionUnavailableException(
						"Transaction not available");
			if (transaction.getOriginalRequest() == null)
				transaction.setOriginalRequest(sipRequest);
			try {
				sipStack.addTransaction(transaction);
			} catch (IOException ex) {
				throw new TransactionUnavailableException(
						"Error sending provisional response");
			}
			// So I can handle timeouts.
			transaction.addEventListener(this);
			if (isAutomaticDialogSupportEnabled()) {
				// If automatic dialog support is enabled then
				// this tx gets his own dialog.
				String dialogId = sipRequest.getDialogId(true);
				SIPDialog dialog = sipStack.getDialog(dialogId);
				if (dialog == null) {
					dialog = sipStack.createDialog(transaction);

				}
				transaction.setDialog(dialog, sipRequest.getDialogId(true));
				if (sipStack.isDialogCreated(sipRequest.getMethod())) {
					sipStack.putInMergeTable(transaction, sipRequest);
				}
				dialog.addRoute(sipRequest);
				if (dialog.getRemoteTag() != null
						&& dialog.getLocalTag() != null) {
					this.sipStack.putDialog(dialog);
				}
			}

		} else {
			if (isAutomaticDialogSupportEnabled()) {
				// Under autmatic dialog support, dialog is tied into a
				// transaction.
				// You cannot create a server tx except for dialog creating
				// transactions.
				// After that, all subsequent transactions are created for you
				// by the stack.
				transaction = (SIPServerTransaction) sipStack.findTransaction(
						(SIPRequest) request, true);
				if (transaction != null)
					throw new TransactionAlreadyExistsException(
							"Transaction exists! ");
				transaction = (SIPServerTransaction) ((SIPRequest) request)
						.getTransaction();
				if (transaction == null)
					throw new TransactionUnavailableException(
							"Transaction not available!");
				if (transaction.getOriginalRequest() == null)
					transaction.setOriginalRequest(sipRequest);
				// Map the transaction.
				try {
					sipStack.addTransaction(transaction);
				} catch (IOException ex) {
					throw new TransactionUnavailableException(
							"Could not send back provisional response!");
				}

				// If there is a dialog already assigned then just update the
				// dialog state.
				String dialogId = sipRequest.getDialogId(true);
				SIPDialog dialog = sipStack.getDialog(dialogId);
				if (dialog != null) {
					dialog.addTransaction(transaction);
					dialog.addRoute(sipRequest);
					transaction.setDialog(dialog, sipRequest.getDialogId(true));
				}

			} else {
				transaction = (SIPServerTransaction) sipStack.findTransaction(
						(SIPRequest) request, true);
				if (transaction != null)
					throw new TransactionAlreadyExistsException(
							"Transaction exists! ");
				transaction = (SIPServerTransaction) ((SIPRequest) request)
						.getTransaction();
				if (transaction != null) {
					if (transaction.getOriginalRequest() == null)
						transaction.setOriginalRequest(sipRequest);
					// Map the transaction.
					sipStack.mapTransaction(transaction);

					// If there is a dialog already assigned then just
					// assign the dialog to the transaction.
					String dialogId = sipRequest.getDialogId(true);
					SIPDialog dialog = sipStack.getDialog(dialogId);
					if (dialog != null) {
						dialog.addTransaction(transaction);
						dialog.addRoute(sipRequest);
						transaction.setDialog(dialog, sipRequest
								.getDialogId(true));
					}

					return transaction;
				} else {
					// tx does not exist so create the tx.

					MessageChannel mc = (MessageChannel) sipRequest
							.getMessageChannel();
					transaction = sipStack.createServerTransaction(mc);
					if (transaction == null)
						throw new TransactionUnavailableException(
								"Transaction unavailable -- too many servrer transactions");

					transaction.setOriginalRequest(sipRequest);
					sipStack.mapTransaction(transaction);

					// If there is a dialog already assigned then just
					// assign the dialog to the transaction.
					String dialogId = sipRequest.getDialogId(true);
					SIPDialog dialog = sipStack.getDialog(dialogId);
					if (dialog != null) {
						dialog.addTransaction(transaction);
						dialog.addRoute(sipRequest);
						transaction.setDialog(dialog, sipRequest
								.getDialogId(true));
					}

					return transaction;
				}
			}

		}
		return transaction;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getSipStack()
	 */
	public SipStack getSipStack() {
		return (SipStack) this.sipStack;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#removeSipListener(javax.sip.SipListener)
	 */
	public void removeSipListener(SipListener sipListener) {
		if (sipListener == this.sipListener) {
			this.sipListener = null;
		}

		boolean found = false;

		for (Iterator<SipProviderImpl> it = sipStack.getSipProviders(); it.hasNext();) {
			SipProviderImpl nextProvider = (SipProviderImpl) it.next();
			if (nextProvider.sipListener != null)
				found = true;
		}
		if (!found) {
			sipStack.sipListener = null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#sendRequest(javax.sip.message.Request)
	 */
	public void sendRequest(Request request) throws SipException {
		if (!sipStack.isAlive())
			throw new SipException("Stack is stopped.");

		// mranga: added check to ensure we are not sending empty (keepalive)
		// message.
		if (((SIPRequest) request).getRequestLine() != null
				&& request.getMethod().equals(Request.ACK)) {
			Dialog dialog = sipStack.getDialog(((SIPRequest) request)
					.getDialogId(false));
			if (dialog != null && dialog.getState() != null) {
				sipStack.getLogWriter().logWarning(
						"Dialog exists -- you may want to use Dialog.sendAck() "
								+ dialog.getState());
			}
		}
		Hop hop = sipStack.getRouter((SIPRequest) request).getNextHop(request);
		if (hop == null)
			throw new SipException("could not determine next hop!");
		SIPRequest sipRequest = (SIPRequest) request;
		// Check if we have a valid via.
		// Null request is used to send default proxy keepalive messages.
		if ((!sipRequest.isNullRequest()) && sipRequest.getTopmostVia() == null)
			throw new SipException("Invalid SipRequest -- no via header!");

		try {
			/*
			 * JvB: Via branch should already be OK, dont touch it here? Some
			 * apps forward statelessly, and then it's not set. So set only when
			 * not set already, dont overwrite CANCEL branch here..
			 */
			if (!sipRequest.isNullRequest()) {
				Via via = sipRequest.getTopmostVia();
				String branch = via.getBranch();
				if (branch == null || branch.length() == 0) {
					via.setBranch(sipRequest.getTransactionId());
				}
			}
			MessageChannel messageChannel = null;
			if (this.listeningPoints.containsKey(hop.getTransport()
					.toUpperCase()))
				messageChannel = sipStack.createRawMessageChannel(
						this.getListeningPoint(hop.getTransport()).getIPAddress(),
						this.getListeningPoint(hop.getTransport()).getPort(), hop);
			if (messageChannel != null) {
				messageChannel.sendMessage((SIPMessage) sipRequest,hop);
			} else {
				throw new SipException(
						"Could not create a message channel for "
								+ hop.toString());
			}
		} catch (IOException ex) {
			if (sipStack.isLoggingEnabled()) {
				sipStack.getLogWriter().logException(ex);
			}

			throw new SipException(
					"IO Exception occured while Sending Request", ex);

		} catch (ParseException ex1) {
			InternalErrorHandler.handleException(ex1);
		} finally {
			if (sipStack.isLoggingEnabled())
				sipStack.getLogWriter().logDebug(
						"done sending " + request.getMethod() + " to hop "
								+ hop);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#sendResponse(javax.sip.message.Response)
	 */
	public void sendResponse(Response response) throws SipException {
		if (!sipStack.isAlive())
			throw new SipException("Stack is stopped");
		SIPResponse sipResponse = (SIPResponse) response;
		Via via = sipResponse.getTopmostVia();
		if (via == null)
			throw new SipException("No via header in response!");
		SIPServerTransaction st = (SIPServerTransaction) sipStack.findTransaction((SIPMessage)response, true);
		if ( st != null   && st.getState() != TransactionState.TERMINATED && this.isAutomaticDialogSupportEnabled()) {
			throw new SipException("Transaction exists -- cannot send response statelessly");
		}
		String transport = via.getTransport();

		// check to see if Via has "received paramaeter". If so
		// set the host to the via parameter. Else set it to the
		// Via host.
		String host = via.getReceived();

		if (host == null)
			host = via.getHost();

		// Symmetric nat support
		int port = via.getRPort();
		if (port == -1) {
			port = via.getPort();
			if (port == -1) {
				if (transport.equalsIgnoreCase("TLS"))
					port = 5061;
				else
					port = 5060;
			}
		}

		// for correct management of IPv6 addresses.
		if (host.indexOf(":") > 0)
			if (host.indexOf("[") < 0)
				host = "[" + host + "]";

		Hop hop = sipStack.getAddressResolver().resolveAddress(
				new HopImpl(host, port, transport));

		try {
			ListeningPointImpl listeningPoint = (ListeningPointImpl) this
					.getListeningPoint(transport);
			if (listeningPoint == null)
				throw new SipException(
						"whoopsa daisy! no listening point found for transport "
								+ transport);
			MessageChannel messageChannel = sipStack.createRawMessageChannel(
					this.getListeningPoint(hop.getTransport()).getIPAddress(),
					listeningPoint.port, hop);
			messageChannel.sendMessage(sipResponse);
		} catch (IOException ex) {
			throw new SipException(ex.getMessage());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#setListeningPoint(javax.sip.ListeningPoint)
	 */
	public synchronized void setListeningPoint(ListeningPoint listeningPoint) {
		if (listeningPoint == null)
			throw new NullPointerException("Null listening point");
		ListeningPointImpl lp = (ListeningPointImpl) listeningPoint;
		lp.sipProvider = this;
		String transport = lp.getTransport().toUpperCase();
		this.address = listeningPoint.getIPAddress();
		this.port = listeningPoint.getPort();
		// This is the first listening point.
		this.listeningPoints.clear();
		this.listeningPoints.put(transport, listeningPoint);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getNewDialog(javax.sip.Transaction)
	 */

	public Dialog getNewDialog(Transaction transaction) throws SipException {
		if (transaction == null)
			throw new NullPointerException("Null transaction!");

		if (!sipStack.isAlive())
			throw new SipException("Stack is stopped.");

		if (isAutomaticDialogSupportEnabled())
			throw new SipException(" Error - AUTOMATIC_DIALOG_SUPPORT is on");

		if (!sipStack.isDialogCreated(transaction.getRequest().getMethod()))
			throw new SipException("Dialog cannot be created for this method "
					+ transaction.getRequest().getMethod());

		SIPDialog dialog = null;
		SIPTransaction sipTransaction = (SIPTransaction) transaction;

		if (transaction instanceof ServerTransaction) {
			SIPServerTransaction st = (SIPServerTransaction) transaction;
			Response response = st.getLastResponse();
			if (response != null) {
				if (response.getStatusCode() != 100)
					throw new SipException(
							"Cannot set dialog after response has been sent");
			}
			SIPRequest sipRequest = (SIPRequest) transaction.getRequest();
			String dialogId = sipRequest.getDialogId(true);
			dialog = sipStack.getDialog(dialogId);
			if (dialog == null) {
				dialog = sipStack.createDialog((SIPTransaction) transaction);
				// create and register the dialog and add the inital route set.
				dialog.addTransaction(sipTransaction);
				dialog.addRoute(sipRequest);
				sipTransaction.setDialog(dialog, null);

			} else {
				sipTransaction.setDialog(dialog, sipRequest.getDialogId(true));
			}
			if (sipStack.isDialogCreated(sipRequest.getMethod())) {
				sipStack.putInMergeTable(st, sipRequest);
			}
		} else {

			SIPClientTransaction sipClientTx = (SIPClientTransaction) transaction;

			SIPResponse response = sipClientTx.getLastResponse();

			if (response == null) {
				// A response has not yet been received, then set this up as the
				// default dialog.
				SIPRequest request = (SIPRequest) sipClientTx.getRequest();

				String dialogId = request.getDialogId(false);
				dialog = sipStack.getDialog(dialogId);
				if (dialog != null) {
					throw new SipException("Dialog already exists!");
				} else {
					dialog = sipStack.createDialog(sipTransaction);
				}
				sipClientTx.setDialog(dialog, null);

			} else {
				throw new SipException(
						"Cannot call this method after response is received!");
			}
		}
		return dialog;

	}

	/**
	 * Invoked when an error has ocurred with a transaction. Propagate up to the
	 * listeners.
	 * 
	 * @param transactionErrorEvent
	 *            Error event.
	 */
	public void transactionErrorEvent(
			SIPTransactionErrorEvent transactionErrorEvent) {
		SIPTransaction transaction = (SIPTransaction) transactionErrorEvent
				.getSource();

		if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TRANSPORT_ERROR) {
			// There must be a way to inform the TU here!!
			if (sipStack.isLoggingEnabled()) {
				sipStack.getLogWriter().logDebug(
						"TransportError occured on " + transaction);
			}
			// Treat this like a timeout event. (Suggestion from Christophe).
			Object errorObject = transactionErrorEvent.getSource();
			Timeout timeout = Timeout.TRANSACTION;
			TimeoutEvent ev = null;

			if (errorObject instanceof SIPServerTransaction) {
				ev = new TimeoutEvent(this, (ServerTransaction) errorObject,
						timeout);
			} else {
			    SIPClientTransaction clientTx = (SIPClientTransaction) errorObject;
			    Hop hop = clientTx.getNextHop();
			    if ( sipStack.getRouter() instanceof RouterExt ) {
			        ((RouterExt) sipStack.getRouter()).transactionTimeout(hop);
			    }
				ev = new TimeoutEvent(this, (ClientTransaction) errorObject,
						timeout);
			}
			// Handling transport error like timeout
			this.handleEvent(ev, (SIPTransaction) errorObject);
		} else if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TIMEOUT_ERROR) {
			// This is a timeout event.
			Object errorObject = transactionErrorEvent.getSource();
			Timeout timeout = Timeout.TRANSACTION;
			TimeoutEvent ev = null;

			if (errorObject instanceof SIPServerTransaction) {
				ev = new TimeoutEvent(this, (ServerTransaction) errorObject,
						timeout);
			} else {
			    SIPClientTransaction clientTx = (SIPClientTransaction) errorObject;
                Hop hop = clientTx.getNextHop();
                if ( sipStack.getRouter() instanceof RouterExt ) {
                    ((RouterExt) sipStack.getRouter()).transactionTimeout(hop);
                }
            
				ev = new TimeoutEvent(this, (ClientTransaction) errorObject,
						timeout);
			}
			this.handleEvent(ev, (SIPTransaction) errorObject);

		} else if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TIMEOUT_RETRANSMIT) {
			// This is a timeout retransmit event.
			// We should never get this if retransmit filter is
			// enabled (ie. in that case the stack should handle.
			// all retransmits.
			Object errorObject = transactionErrorEvent.getSource();
			Transaction tx = (Transaction) errorObject;

			if (tx.getDialog() != null)
				InternalErrorHandler.handleException("Unexpected event !",
						this.sipStack.getLogWriter());

			Timeout timeout = Timeout.RETRANSMIT;
			TimeoutEvent ev = null;

			if (errorObject instanceof SIPServerTransaction) {
				ev = new TimeoutEvent(this, (ServerTransaction) errorObject,
						timeout);
			} else {
				ev = new TimeoutEvent(this, (ClientTransaction) errorObject,
						timeout);
			}
			this.handleEvent(ev, (SIPTransaction) errorObject);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#getListeningPoints()
	 */
	public synchronized ListeningPoint[] getListeningPoints() {

		ListeningPoint[] retval = new ListeningPointImpl[this.listeningPoints
				.size()];
		this.listeningPoints.values().toArray(retval);
		return retval;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#addListeningPoint(javax.sip.ListeningPoint)
	 */
	public synchronized void addListeningPoint(ListeningPoint listeningPoint)
			throws ObjectInUseException {
		ListeningPointImpl lp = (ListeningPointImpl) listeningPoint;
		if (lp.sipProvider != null && lp.sipProvider != this)
			throw new ObjectInUseException(
					"Listening point assigned to another provider");
		String transport = lp.getTransport().toUpperCase();
		if (this.listeningPoints.isEmpty()) {
			// first one -- record the IP address/port of the LP

			this.address = listeningPoint.getIPAddress();
			this.port = listeningPoint.getPort();
		} else {
			if ((!this.address.equals(listeningPoint.getIPAddress()))
					|| this.port != listeningPoint.getPort())
				throw new ObjectInUseException(
						"Provider already has different IP Address associated");

		}
		if (this.listeningPoints.containsKey(transport)
				&& this.listeningPoints.get(transport) != listeningPoint)
			throw new ObjectInUseException(
					"Listening point already assigned for transport!");

		// This is for backwards compatibility.
		lp.sipProvider = this;

		this.listeningPoints.put(transport, lp);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#removeListeningPoint(javax.sip.ListeningPoint)
	 */
	public synchronized void removeListeningPoint(ListeningPoint listeningPoint)
			throws ObjectInUseException {
		ListeningPointImpl lp = (ListeningPointImpl) listeningPoint;
		if (lp.messageProcessor.inUse())
			throw new ObjectInUseException("Object is in use");
		this.listeningPoints.remove(lp.getTransport().toUpperCase());

	}

	/**
	 * Remove all the listening points for this sip provider. This is called
	 * when the stack removes the Provider
	 */
	public synchronized void removeListeningPoints() {
		for (Iterator it = this.listeningPoints.values().iterator(); it
				.hasNext();) {
			ListeningPointImpl lp = (ListeningPointImpl) it.next();
			lp.messageProcessor.stop();
			it.remove();
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.SipProvider#setAutomaticDialogSupportEnabled(boolean)
	 */
	public void setAutomaticDialogSupportEnabled(
			boolean automaticDialogSupportEnabled) {
		this.automaticDialogSupportEnabled = automaticDialogSupportEnabled;
	}

	/**
	 * @return Returns the automaticDialogSupportEnabled.
	 */
	public boolean isAutomaticDialogSupportEnabled() {
		return automaticDialogSupportEnabled;
	}

	

}
